package com.atguigu.gmall.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.HttpClientUtil;
import com.atguigu.gmall.model.enums.OrderStatus;
import com.atguigu.gmall.model.enums.ProcessStatus;
import com.atguigu.gmall.model.order.OrderDetail;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.order.mapper.OrderDetailMapper;
import com.atguigu.gmall.order.mapper.OrderInfoMapper;
import com.atguigu.gmall.order.service.OrderService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderService {

    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Value("${ware.url}")
    private String WARE_URL;// 获取仓库地址 http://localhost:9001

    @Autowired
    private RabbitService rabbitService;


    /**
     * 保存订单信息
     *
     * @param orderInfo
     * @return 返回订单号
     */
    @Override
    @Transactional
    public Long saveOrderInfo(OrderInfo orderInfo) {

         /*
         保存订单信息 需要保存两张表：订单表 和 订单明细表

         页面没有传递，订单表目前需要赋值的数据：
         total_amount、order_status、userId、out_trade_no、trade_body、create_time、expire_time、process_status
         */

        // 计算并设置 total_amount 有 orderInfo 且 orderDetailList 属性有值 直接调用方法计算总金额
        orderInfo.sumTotalAmount();

        // 设置订单状态
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());

        // 设置 userId 【在实现类无法获得 但是可以在控制器获取 在控制器赋值 userId】

        // 设置 out_trade_no 订单交易编号（第三方支付用)
        String outTradeNo = "ATGUIGU" + System.currentTimeMillis() + "" + new Random().nextInt(1000);
        orderInfo.setOutTradeNo(outTradeNo);

        // 设置 trade_body 订单描述(第三方支付用)：拼接商品名称
        // 获取订单明细
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        // 定义变量 记录拼接的商品名称
        StringBuilder tradeBody = new StringBuilder();
        // 循环遍历获取每个商品明细的名称
        for (OrderDetail orderDetail : orderDetailList) {
            tradeBody.append(orderDetail.getSkuName());
        }
        // 注意拼接名称的长度不能超过数据库中字段的长度
        if (tradeBody.toString().length() > 200) {
            String tradeBodyAfterSub = tradeBody.toString().substring(0, 200);
            orderInfo.setTradeBody(tradeBodyAfterSub);
        } else {
            orderInfo.setTradeBody(tradeBody.toString());
        }

        // 设置 create_time 创建时间
        orderInfo.setCreateTime(new Date());

        // 设置 expire_time 失效时间
           /*
            失效时间 一般考虑库存设置默认时间的长短
         */
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 1);
        orderInfo.setExpireTime(calendar.getTime());

        // 设置 process_status 进度状态【包含订单状态】
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());

        // 保存 订单表 到 数据库
        orderInfoMapper.insert(orderInfo);

        // 获取订单明细进行赋值
        List<OrderDetail> orderDetails = orderInfo.getOrderDetailList();
        // 循环遍历为订单明细设置页面没有传递的 订单编号
        for (OrderDetail orderDetail : orderDetails) {
             /*
            需要赋值：OrderId
            但是我们也没有手动设置 orderInfo.Id，在此处  orderDetail.setOrderId(orderInfo.getId()) 会报空指针吗？
            不会报空指针，因为在 orderInfoMapper.insert(orderInfo) 的时候 id 是主键是自增的。当插入订单表成功后，就会自动生成id。
             */

            // 赋值：OrderId
            orderDetail.setOrderId(orderInfo.getId());

            // 保存 订单明细表 到 数据库
            orderDetailMapper.insert(orderDetail);
        }

        // 发送延迟消息 如果定时未支付 取消订单
        // 发送的内容：取消订单 - 本质是更新订单的状态，是根据订单Id来更新的。故发送的消息为：订单Id
        rabbitService.sendDelayMessage(
                MqConst.EXCHANGE_DIRECT_ORDER_CANCEL,
                MqConst.ROUTING_ORDER_CANCEL,
                orderInfo.getId(),
                MqConst.DELAY_TIME);

        // 返回订单编号
        return orderInfo.getId();
    }

    /**
     * 生成流水号【传递到页面和保存进缓存】
     *
     * @param userId 传递用户Id 用于将流水号存储到缓存
     * @return
     */
    @Override
    public String getTradeNo(String userId) {
        // 创建一个流水号
        String tradeNo = userId + UUID.randomUUID().toString();

        // 定义缓存中的 key
        String tradeNoKey = "user:" + userId + ":tradeCode";

        // 保存流水号到缓存
        redisTemplate.opsForValue().set(tradeNoKey, tradeNo);

        // 返回流水号
        return tradeNo;
    }

    /**
     * 比较流水号
     *
     * @param userId      获取缓存中的流水号
     * @param tradeCodeNo 页面传递过来的流水号
     * @return
     */
    @Override
    public boolean checkTradeCode(String userId, String tradeCodeNo) {
        // 定义缓存中的 key
        String tradeNoKey = "user:" + userId + ":tradeCode";

        // 获取缓存中的流水号
        String tradeNoInRedis = (String) redisTemplate.opsForValue().get(tradeNoKey);

        // 返回比较结果
        return tradeCodeNo.equals(tradeNoInRedis);
    }

    /**
     * 删除流水号
     *
     * @param userId 获取缓存中的流水号
     */
    @Override
    public void deleteTradeNo(String userId) {
        // 定义缓存中的 key
        String tradeNoKey = "user:" + userId + ":tradeCode";
        redisTemplate.delete(tradeNoKey);
    }

    /**
     * 远程调用 验证库存
     *
     * @param skuId
     * @param skuNum
     * @return
     */
    @Override
    public boolean checkStock(Long skuId, Integer skuNum) {
        // 远程调用 http://localhost:9001/hasStock?skuId=10221&num=2
        String result = HttpClientUtil.doGet(WARE_URL + "/hasStock?skuId=" + skuId + "&num=" + skuNum);
        // 返回结果
        return "1".equals(result);
    }

    /**
     * 根据订单Id 处理过期订单
     *
     * @param orderId
     */
    @Override
    public void execExpiredOrder(Long orderId) {
        // 方式一：
         /*OrderInfo orderInfo = new OrderInfo();

        orderInfo.setId(orderId);

        // 订单状态可以根据 进程状态更改
        //orderInfo.setOrderStatus(OrderStatus.CLOSED.name());
        orderInfo.setOrderStatus(ProcessStatus.CLOSED.getOrderStatus().name());
        orderInfo.setProcessStatus(ProcessStatus.CLOSED.name());

        orderInfoMapper.updateById(orderInfo);*/

        // 更新 OrderInfo 状态 方式二：根据订单Id、进程状态对象修改订单状态
        updateOrderStatus(orderId, ProcessStatus.CLOSED);

        // 更新 paymentInfo 状态 - 发送消息
        rabbitService.sendMessage(
                MqConst.EXCHANGE_DIRECT_PAYMENT_CLOSE,
                MqConst.ROUTING_PAYMENT_CLOSE,
                orderId);

    }

    /**
     * 根据订单Id、进程状态对象修改订单状态
     * 后续的支付完成、发货的时候更新订单状态，传入对应的 进程状态对象 即可实现更新
     *
     * @param orderId
     * @param processStatus
     */
    public void updateOrderStatus(Long orderId, ProcessStatus processStatus) {
        OrderInfo orderInfo = new OrderInfo();

        orderInfo.setId(orderId);

        // 订单状态根据进程状态更改
        orderInfo.setOrderStatus(processStatus.getOrderStatus().name());
        //orderInfo.setProcessStatus(ProcessStatus.CLOSED.name());
        orderInfo.setProcessStatus(processStatus.name());

        orderInfoMapper.updateById(orderInfo);
    }

    /**
     * 根据订单Id 查询订单信息和订单明细信息
     *
     * @param orderId
     * @return
     */
    @Override
    public OrderInfo getOrderInfo(Long orderId) {
        // 查询订单信息
        OrderInfo orderInfo = orderInfoMapper.selectById(orderId);

        // 查询订单明细信息
        QueryWrapper<OrderDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_id", orderId);

        List<OrderDetail> orderDetailList = orderDetailMapper.selectList(queryWrapper);

        // 设置订单明细信息
        orderInfo.setOrderDetailList(orderDetailList);

        return orderInfo;
    }

    /**
     * 发送消息 - 通知减库存
     *
     * @param orderId
     */
    @Override
    public void sendOrderStatus(Long orderId) {
        // 将订单的状态改为【已通知仓库】
        updateOrderStatus(orderId, ProcessStatus.NOTIFIED_WARE);

        // 需要发送消息的字符串
        String wareJson = initWareOrder(orderId);

        // 发送消息给库存
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_WARE_STOCK,
                MqConst.ROUTING_WARE_STOCK,
                wareJson);
    }

    /**
     * 根据 orderId 获取 json 字符串
     *
     * @param orderId
     * @return
     */
    private String initWareOrder(Long orderId) {
        // 通过 orderId 获取 OrderInfo
        OrderInfo orderInfo = getOrderInfo(orderId);

        // 获取减库存接口需要的参数{只需要的部分数据，不是全部的orderInfo数据}

        // 调用方法获取 orderInfo 数据 对应的 map
        Map map = initWareOrder(orderInfo);

        return JSON.toJSONString(map);
    }

    /**
     * 将 orderInfo 部分数据转换为 Map
     *
     * @param orderInfo
     * @return
     */
    public Map initWareOrder(OrderInfo orderInfo) {

        Map<String, Object> map = new HashMap<>();

        map.put("orderId", orderInfo.getId());
        map.put("consignee", orderInfo.getConsignee());
        map.put("consigneeTel", orderInfo.getConsigneeTel());
        map.put("orderComment", orderInfo.getOrderComment());
        map.put("orderBody", orderInfo.getTradeBody());
        map.put("deliveryAddress", orderInfo.getDeliveryAddress());
        map.put("paymentWay", "2");
        map.put("wareId", orderInfo.getWareId());// 仓库Id 减库存拆单时需要使用！

         /*
         减库存接口 details 参数里数据格式为：k-v，多个数据使用list保存。故为：List<Map>。
            details:[{skuId:101,skuNum:1,skuName:’小米手64G’},
                     {skuId:201,skuNum:1,skuName:’索尼耳机’}]

         */
        // 获取 订单明细集合
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();

        // 声明一个集合存储 订单明细Map
        List<Map> mapArrayList = new ArrayList<>();

        for (OrderDetail orderDetail : orderDetailList) {
            // 创建 Map 封装 details 参数需要的数据
            Map<String, Object> orderDetailMap = new HashMap<>();

            orderDetailMap.put("skuId", orderDetail.getSkuId());
            orderDetailMap.put("skuNum", orderDetail.getSkuNum());
            orderDetailMap.put("skuName", orderDetail.getSkuName());

            // 将 orderDetailMap 保存进集合
            mapArrayList.add(orderDetailMap);
        }

        // 将数据存入map
        map.put("details", mapArrayList);

        // 返回 map
        return map;
    }

    /**
     * 拆单
     *
     * @param orderId    订单号
     * @param wareSkuMap 仓库编号与商品的对照关系
     * @return
     */
    @Override
    public List<OrderInfo> orderSplit(Long orderId, String wareSkuMap) {

        List<OrderInfo> subOrderInfoList = new ArrayList<>();

            /*
            1 获取原始订单
            2 将 wareSkuMap 转换为可操作的对象

                [{"wareId":"1","skuIds":["2","10"]},{"wareId":"2","skuIds":["3"]}] ===>>> List<Map>
            3 创建子订单对象
            4 为子订单对象赋值{需要赋值orderInfo信息 和 OrderDetail信息}
            5 保存子订单数据
            6 将子订单数据放入集合
            7 更改原始订单状态为 SPLIT*/

        // 1 获取原始订单
        OrderInfo orderInfoOriginal = getOrderInfo(orderId);

        // 2 将 wareSkuMap 转换为可操作的对象
        List<Map> wareSkuMapList = JSON.parseArray(wareSkuMap, Map.class);

        if (wareSkuMapList != null) {

            // 遍历 wareSkuMapList
            for (Map map : wareSkuMapList) {

                // 获取仓库Id
                String wareId = (String) map.get("wareId");

                // 获取购买商品的 skuId
                List<String> skuIds = (List<String>) map.get("skuIds");

                // 3 创建子订单对象
                OrderInfo subOrderInfo = new OrderInfo();

                // 4 为子订单对象赋值
                // 拷贝原始订单部分数据到子订单对象
                BeanUtils.copyProperties(orderInfoOriginal, subOrderInfo);

                subOrderInfo.setId(null);// 子订单对象主键自增
                subOrderInfo.setParentOrderId(orderId);// 设置父Id
                subOrderInfo.setWareId(wareId);// 设置仓库Id

                // 获取原始订单中的订单明细
                List<OrderDetail> orderDetailList = orderInfoOriginal.getOrderDetailList();

                // 创建集合保存子订单的订单明细
                List<OrderDetail> subOrderDetailList = new ArrayList<>();

                if (orderDetailList != null && orderDetailList.size() > 0) {

                    for (OrderDetail orderDetail : orderDetailList) {// 遍历订单明细集合

                        for (String skuId : skuIds) {// 遍历购买商品的skuId集合

                            if (Long.parseLong(skuId) == orderDetail.getSkuId()) {// 判断仓库中的skuId与订单明细Id
                                // skuId相同 处于同一个仓库
                                subOrderDetailList.add(orderDetail);
                            }
                        }
                    }
                }
                subOrderInfo.setOrderDetailList(subOrderDetailList); // 赋值订单明细 用于计算商品总金额

                subOrderInfo.sumTotalAmount(); // 赋值订单明细 用于计算商品总金额

                // 5 保存子订单数据
                saveOrderInfo(subOrderInfo);

                // 6 将子订单数据放入集合
                subOrderInfoList.add(subOrderInfo);
            }
        }
        // 7 更改原始订单状态为 SPLIT
        updateOrderStatus(orderId, ProcessStatus.SPLIT);

        return subOrderInfoList;
    }

    /**
     * 根据订单Id 处理过期订单
     * @param orderId
     * @param flag 2代表需要关闭 orderInfo 和 paymentInfo;1代表只需要关闭orderInfo
     */
    @Override
    public void execExpiredOrder(Long orderId, String flag) {
        // 关闭 orderInfo
        updateOrderStatus(orderId,ProcessStatus.CLOSED);

        // flag == 2 需要关闭 paymentInfo
        if ("2".equals(flag)){
            rabbitService.sendMessage(
                    MqConst.EXCHANGE_DIRECT_PAYMENT_CLOSE,
                    MqConst.ROUTING_PAYMENT_CLOSE,
                    orderId);
        }
    }
}
